/*
 * Copyright 2009 - sanatkumar.padhy
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *  
 *  	http://www.apache.org/licenses/LICENSE-2.0
 *  
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS, 
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and 
 *  limitations under the License. 
 */
package com.googlecode.simplecrud.generator.db;

import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import com.googlecode.simplecrud.generator.GeneratorException;

/**
 * This utility class introspects the DB and resolves a javaType for each
 * column.
 * 
 * @author sanatkumar.padhy
 */
public class DbIntrospector
{

	/** A database connection object */
	private Connection connection;

	/**
	 * Retrieves a map of column and its Metadata
	 * 
	 * @param tableName
	 *            - the table to be introspected
	 * @return <code>Map<String, ColumnMetadata></code>
	 */
	public Map<String, ColumnMetadata> getIntrospectedTable(String tableName)
	{
		Map<String, ColumnMetadata> columnMap = new HashMap<String, ColumnMetadata>();
		ColumnMetadata column = null;
		String columnName = null;
		try {
			DatabaseMetaData metadata = getConnection().getMetaData();
			ResultSet resultSet = metadata.getColumns(null, null, tableName, null);
			while (resultSet.next()) {
				column = new ColumnMetadata();
				columnName = resultSet.getString("COLUMN_NAME").toUpperCase();
				column.setColumnName(columnName);
				column.setLength(resultSet.getInt("COLUMN_SIZE"));
				column.setJdbcType(resultSet.getInt("DATA_TYPE"));
				column.setScale(resultSet.getInt("DECIMAL_DIGITS"));
				column.setJavaType(resolveToJavaType(column));
				columnMap.put(columnName, column);
			}
		}
		catch (SQLException e) {
			throw new GeneratorException("Error Introspecting DB :: ", e);
		}

		return columnMap;
	}

	/* Resolve Java Type for a given column */
	private String resolveToJavaType(ColumnMetadata column)
	{
		switch (column.getJdbcType()) {
		case Types.ARRAY:
			return Object.class.getName();

		case Types.BIGINT:
			return Long.class.getName();

		case Types.BINARY:
		case Types.BLOB:
		case Types.LONGVARBINARY:
		case Types.VARBINARY:
			return "byte[]";

		case Types.BIT:
		case Types.BOOLEAN:
			return Boolean.class.getName();

		case Types.CHAR:
		case Types.CLOB:
		case Types.VARCHAR:
		case Types.LONGVARCHAR:
			return String.class.getName();

		case Types.DECIMAL:
		case Types.NUMERIC:
			if ((column.getScale() > 0) || (column.getLength() > 18)) {
				return BigDecimal.class.getName();
			}
			else if (column.getLength() > 9) {
				return Long.class.getName();
			}
			else if (column.getLength() > 4) {
				return Integer.class.getName();
			}
			else {
				return Short.class.getName();
			}

		case Types.DOUBLE:
		case Types.FLOAT:
			return Double.class.getName();

		case Types.INTEGER:
			return Integer.class.getName();

		case Types.REAL:
			return Float.class.getName();

		case Types.REF:
		case Types.STRUCT:
			return Object.class.getName();

		case Types.SMALLINT:
			return Short.class.getName();

		case Types.TIME:
		case Types.TIMESTAMP:
		case Types.DATE:
			return Date.class.getName();

		case Types.TINYINT:
			return Byte.class.getName();

		default:
			return null;
		}
	}

	/**
	 * @return the connection
	 */
	public Connection getConnection()
	{
		return connection;
	}

	/**
	 * @param connection
	 *            the connection to set
	 */
	public void setConnection(Connection connection)
	{
		this.connection = connection;
	}
}
